﻿using StarUtils.Extension;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace StarUtils.Http
{
    public abstract class HttpRequestBase<TRequest> where TRequest : IRequest<TRequest>
    {
        private string _url;

        private readonly HttpMethod _httpMethod;

        private IDictionary<string, object> _params;

        private string _data;

        private Encoding _encoding;

        private string _contentType;

        private readonly CookieContainer _cookieContainer;

        private TimeSpan _timeout;

        private readonly Dictionary<string, string> _headers;

        private Action<string> _failAction;

        private Action<string, HttpStatusCode> _failStatusCodeAction;

        private Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> _serverCertificateCustomValidationCallback;

        private string _token;

        private readonly IList<IFileParameter> _files;

        private readonly HttpClient _client;

        protected HttpRequestBase(HttpMethod httpMethod, string url, HttpClient client)
        {
            if (string.IsNullOrWhiteSpace(url))
            {
                throw new ArgumentNullException("url");
            }

            System.Text.Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            _url = url;
            _httpMethod = httpMethod;
            _params = new Dictionary<string, object>();
            _contentType = HttpContentType.FormUrlEncoded.Description();
            _cookieContainer = new CookieContainer();
            _timeout = new TimeSpan(0, 0, 30);
            _headers = new Dictionary<string, string>();
            _encoding = System.Text.Encoding.UTF8;
            _files = new List<IFileParameter>();
            _client = client;
        }

        private TRequest This()
        {
            return (TRequest)(object)this;
        }

        public TRequest Encoding(Encoding encoding)
        {
            _encoding = encoding;
            return This();
        }

        public TRequest Encoding(string encoding)
        {
            return Encoding(System.Text.Encoding.GetEncoding(encoding));
        }

        public TRequest ContentType(HttpContentType contentType)
        {
            return ContentType(contentType.Description());
        }

        public TRequest ContentType(string contentType)
        {
            _contentType = contentType;
            return This();
        }

        public TRequest Cookie(string name, string value, double expiresDate)
        {
            return Cookie(name, value, null, null, DateTime.Now.AddDays(expiresDate));
        }

        public TRequest Cookie(string name, string value, DateTime expiresDate)
        {
            return Cookie(name, value, null, null, expiresDate);
        }

        public TRequest Cookie(string name, string value, string path = "/", string domain = null, DateTime? expiresDate = null)
        {
            return Cookie(new Cookie(name, value, path, domain)
            {
                Expires = expiresDate ?? DateTime.Now.AddYears(1)
            });
        }

        public TRequest Cookie(Cookie cookie)
        {
            _cookieContainer.Add(new Uri(_url), cookie);
            return This();
        }

        public TRequest Timeout(int timeout)
        {
            _timeout = new TimeSpan(0, 0, timeout);
            return This();
        }

        public TRequest Header<T>(string key, T value)
        {
            _headers.Add(key, value.SafeString());
            return This();
        }

        public TRequest Data(IDictionary<string, object> parameters)
        {
            _params = parameters ?? throw new ArgumentNullException("parameters");
            return This();
        }

        public TRequest Data(string key, object value)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException("key");
            }

            if (string.IsNullOrWhiteSpace(value.SafeString()))
            {
                return This();
            }

            _params.Add(key, value);
            return This();
        }

        public TRequest JsonData<T>(T value)
        {
            ContentType(HttpContentType.Json);
            _data = typeof(T) == typeof(string) ? value.ToString() : JsonHelper.ToJson(value);
            return This();
        }

        public TRequest XmlData(string value)
        {
            ContentType(HttpContentType.Xml);
            _data = value;
            return This();
        }

        public TRequest FileData(string filePath)
        {
            return FileData("files", filePath);
        }

        public TRequest FileData(string name, string filePath)
        {
            ContentType(HttpContentType.FormData);
            _files.Add(new PhysicalFileParameter(filePath, name));
            return This();
        }

        public TRequest OnFail(Action<string> action)
        {
            _failAction = action;
            return This();
        }

        public TRequest OnFail(Action<string, HttpStatusCode> action)
        {
            _failStatusCodeAction = action;
            return This();
        }

        public TRequest IgnoreSsl()
        {
            _serverCertificateCustomValidationCallback = (a, b, c, d) => true;
            return This();
        }

        public TRequest BearerToken(string token)
        {
            _token = token;
            return This();
        }

        public async Task<HttpResponseMessage> ResponseAsync()
        {
            SendBefore();
            return await SendAsync();
        }

        public async Task<string> ResultAsync()
        {
            SendBefore();
            HttpResponseMessage response = await SendAsync();
            string result = await response.Content.ReadAsStringAsync();
            SendAfter(result, response);
            return result;
        }

        public async Task<Stream> ResultStream()
        {
            SendBefore();
            return await (await SendAsync()).Content.ReadAsStreamAsync();
        }

        public async Task<byte[]> ResultBytes()
        {
            SendBefore();
            return await (await SendAsync()).Content.ReadAsByteArrayAsync();
        }

        public virtual void SendBefore()
        {
        }

        protected async Task<HttpResponseMessage> SendAsync()
        {
            HttpClient httpClient = CreateHttpClient();
            InitHttpClient(httpClient);
            if (_client == null)
            {
                return await httpClient.SendAsync(CreateRequestMessage());
            }

            HttpContent content = CreateHttpContent();
            foreach (KeyValuePair<string, string> header in _headers)
            {
                httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
            }

            if (_httpMethod == HttpMethod.Get)
            {
                BuildParam();
                return await httpClient.GetAsync(_url);
            }

            if (_httpMethod == HttpMethod.Post)
            {
                return await httpClient.PostAsync(_url, content);
            }

            if (_httpMethod == HttpMethod.Put)
            {
                return await httpClient.PutAsync(_url, content);
            }

            if (_httpMethod == HttpMethod.Delete)
            {
                return await httpClient.DeleteAsync(_url);
            }

            return null;
        }

        protected virtual HttpClient CreateHttpClient()
        {
            if (_client != null)
            {
                return _client;
            }

            return HttpClientBuilderFactory.CreateClient(_url, _timeout);
        }

        protected virtual void InitHttpClient(HttpClient client)
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
        }

        protected virtual HttpRequestMessage CreateRequestMessage()
        {
            HttpRequestMessage httpRequestMessage = new HttpRequestMessage
            {
                Method = _httpMethod,
                Content = CreateHttpContent(),
                RequestUri = new Uri(_url)
            };
            foreach (KeyValuePair<string, string> header in _headers)
            {
                httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            return httpRequestMessage;
        }

        private HttpContent CreateHttpContent()
        {
            string text = _contentType.SafeString().ToLower();
            return text switch
            {
                "application/x-www-form-urlencoded" => new FormUrlEncodedContent(_params.ToDictionary((t) => t.Key, (t) => t.Value.SafeString())),
                "application/json" => CreateJsonContent(),
                "text/xml" => CreateXmlContent(),
                "multipart/form-data" => CreateMultipartFormDataContent(),
                "application/grpc" => CreateGrpcContent(),
                _ => throw new NotImplementedException("未实现该 '" + text + "' ContentType"),
            };
        }

        private HttpContent CreateGrpcContent()
        {
            if (_httpMethod == HttpMethod.Get)
            {
                BuildParam();
                return null;
            }

            if (string.IsNullOrWhiteSpace(_data))
            {
                _data = JsonHelper.ToJson(_params);
            }

            return new StringContent(_data, _encoding, "application/grpc");
        }

        private HttpContent CreateJsonContent()
        {
            if (string.IsNullOrWhiteSpace(_data))
            {
                _data = JsonHelper.ToJson(_params);
            }

            return new StringContent(_data, _encoding, "application/json");
        }

        private HttpContent CreateXmlContent()
        {
            return new StringContent(_data, _encoding, "text/xml");
        }

        private HttpContent CreateMultipartFormDataContent()
        {
            MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
            foreach (IFileParameter file in _files)
            {
                multipartFormDataContent.Add(new StreamContent(file.GetFileStream()), file.GetName(), file.GetFileName());
            }

            return multipartFormDataContent;
        }

        private void BuildParam()
        {
            if (_params == null || _params.Count <= 0)
            {
                return;
            }

            if (HttpUtility.ParseQueryString(_url).Count > 1)
            {
                _url += "&";
            }
            else
            {
                _url += "?";
            }

            string text = "";
            foreach (KeyValuePair<string, object> param in _params)
            {
                text += $"{Encode(param.Key)}={Encode(param.Value.SafeString())}&";
            }

            if (text != "")
            {
                text = text.TrimEnd('&');
            }

            _url += text;
        }

        private string Encode(string content)
        {
            return HttpUtility.UrlEncode(content, _encoding);
        }

        protected virtual void SendAfter(string result, HttpResponseMessage response)
        {
            string contentType = GetContentType(response);
            if (response.IsSuccessStatusCode)
            {
                SuccessHandler(result, response.StatusCode, contentType);
            }
            else
            {
                FailHandler(result, response.StatusCode, contentType);
            }
        }

        private string GetContentType(HttpResponseMessage response)
        {
            if (response?.Content?.Headers?.ContentType != null)
            {
                return response.Content.Headers.ContentType!.MediaType;
            }

            return string.Empty;
        }

        protected virtual void SuccessHandler(string result, HttpStatusCode statusCode, string contentType)
        {
        }

        protected virtual void FailHandler(string result, HttpStatusCode statusCode, string contentType)
        {
            _failAction?.Invoke(result);
            _failStatusCodeAction?.Invoke(result, statusCode);
        }
    }
}
